package service

import (
	"context"
	"errors"
	"fmt"
	"github.com/wuzfei/go-helper/rand"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"net"
	"onvif_server/api/camera"
	"onvif_server/internal/errcode"
	"onvif_server/internal/library/setting"
	"onvif_server/internal/model"
	"onvif_server/internal/repository"
	"onvif_server/internal/utils"
	"onvif_server/pkg/m3u8"
	"onvif_server/pkg/onvif"
	"onvif_server/pkg/subscribe"
	"onvif_server/pkg/zlmedia_sdk"
	"path/filepath"
	"strings"
	"time"
)

type StreamType int32

const (
	MAIN_STREAM StreamType = 0 // 主码流
	SUB_STREAM  StreamType = 1 // 辅码流
)

type CameraService interface {
	List(ctx context.Context, req *camera.ListReq) (total int64, res []*model.Camera, err error)
	Create(ctx context.Context, req *camera.CreateReq) error
	Update(ctx context.Context, req *camera.UpdateReq) error
	Detail(ctx context.Context, req *camera.IdOrDeviceIdReq) (res *model.Camera, err error)
	Delete(ctx context.Context, id []int) error

	SyncDatetime(ctx context.Context, req *camera.IdOrDeviceIdReq) (err error)
	GetDatetime(ctx context.Context, req *camera.IdOrDeviceIdReq) (datetime time.Time, err error)
	Discovery(ctx context.Context) (res []*camera.DiscoveryCameraListRes, err error)
	CheckConnection(ctx context.Context, req *onvif.CameraConnection) (res *onvif.CameraInfo, err error)
	GetM3u8(ctx context.Context, req *camera.GetM3u8Req, dayOrHour string) (res []string, err error)
	GetMp4ByImg(ctx context.Context, req *camera.GetMp4ByImgReq) (res string, err error)
	GetHistory(ctx context.Context, req *camera.GetHistoryReq) (res *camera.GetHistoryRes, err error)
	SearchVideo(ctx context.Context, req *camera.VideoSearchReq) (res []m3u8.HistoryFile, err error)
	GetSnapUrl(ctx context.Context, req *camera.GetSnapUrlReq) (res string, err error)

	// 定时任务
	CheckCameraOnline(ctx context.Context)
	UpdateCameraCoverImg(ctx context.Context)
	AutoHandleMedia(ctx context.Context)
}

func NewCameraService(service *Service, cameraRepo repository.CameraRepository, subRepo repository.SubscriptionRepository, onvif *onvif.Onvif, subLib *subscribe.Subscribe, zlMedia *zlmedia_sdk.ZLMediaClient) CameraService {
	return &cameraService{
		cameraRepo: cameraRepo,
		subRepo:    subRepo,
		Service:    service,
		zlMedia:    zlMedia,
		onvif:      onvif,
		subLib:     subLib,
		m3u8:       m3u8.NewM3u8(service.config.CameraRootPath, service.log, service.loc),
	}
}

type cameraService struct {
	cameraRepo repository.CameraRepository
	subRepo    repository.SubscriptionRepository
	onvif      *onvif.Onvif
	m3u8       *m3u8.M3u8
	zlMedia    *zlmedia_sdk.ZLMediaClient
	subLib     *subscribe.Subscribe
	*Service
}

func (s *cameraService) List(ctx context.Context, req *camera.ListReq) (total int64, res []*model.Camera, err error) {
	return s.cameraRepo.List(ctx, req.Keyword, req.PageQuery())
}

func (s *cameraService) Detail(ctx context.Context, req *camera.IdOrDeviceIdReq) (res *model.Camera, err error) {
	if req.Id > 0 {
		return s.cameraRepo.GetByID(ctx, req.Id)
	} else if req.DeviceId != "" {
		return s.cameraRepo.GetByDeviceId(ctx, req.DeviceId)
	}
	return nil, errcode.ErrBadRequest
}

func (s *cameraService) Create(ctx context.Context, req *camera.CreateReq) error {
	if req.DeviceId == "" {
		req.DeviceId = strings.ToUpper(rand.StringN(12))
	}
	m, err := s.cameraRepo.GetByDeviceId(ctx, req.DeviceId)
	if err != nil {
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			return err
		}
	}
	if m != nil && m.ID != 0 {
		return errors.New("该摄像头已经添加")
	}

	if req.Point != nil {
		if req.Point.Valid() {
			m.Point = *req.Point
		}
	}

	m.Alias = req.Alias
	m.Build = req.Build
	m.Storey = req.Storey
	m.Location = req.Location
	m.Username = req.Username
	m.Password = req.Password
	m.Record = req.Record
	m.UUID = req.UUID
	m.RtspUri = req.RtspUri
	m.RtspUri1 = req.RtspUri1
	m.RtspUri2 = req.RtspUri2
	m.DeviceId = req.DeviceId
	m.Addx = req.Addx

	m.Filepath = filepath.Join(s.config.CameraRootPath, m.DeviceId)
	m.Key = utils.GetRealTimePlayId()
	//获取截图地址
	m.SnapUrl, err = s.GetSnapUrl(ctx, &camera.GetSnapUrlReq{
		DeviceId: m.DeviceId,
		Addx:     m.Addx,
		Username: m.Username,
		Password: m.Password,
	})
	if err != nil {
		return err
	}

	err = s.cameraRepo.Create(ctx, m)
	if err != nil {
		return err
	}
	//todo 添加摄像头事件处理
	// event()
	go func() {
		_err := s.subLib.Push(WithTopicToSubscribers(s.subRepo.Subscriber(context.Background()), subscribe.TopicCameraCreate), &subscribe.CameraCreatePayload{m})
		if _err != nil {
			s.log.Error("推送创建摄像头消息失败", zap.Error(_err))
		}
	}()
	return nil
}

func (s *cameraService) Update(ctx context.Context, req *camera.UpdateReq) error {
	m, err := s.cameraRepo.GetByDeviceId(ctx, req.DeviceId)
	if err != nil {
		return err
	}
	if m.ID == 0 {
		return errors.New("用户不存在")
	}

	if req.Point != nil {
		if req.Point.Valid() {
			m.Point = *req.Point
		}
	}

	m.Alias = req.Alias
	m.Build = req.Build
	m.Storey = req.Storey
	m.Location = req.Location
	m.Username = req.Username
	m.Password = req.Password
	m.Record = req.Record
	m.UUID = req.UUID
	m.RtspUri = req.RtspUri
	m.RtspUri1 = req.RtspUri1
	m.RtspUri2 = req.RtspUri2
	m.Addx = req.Addx
	//获取截图地址
	m.SnapUrl, err = s.GetSnapUrl(ctx, &camera.GetSnapUrlReq{
		DeviceId: m.DeviceId,
		Addx:     m.Addx,
		Username: m.Username,
		Password: m.Password,
	})
	if err != nil {
		return err
	}

	err = s.cameraRepo.Update(ctx, m, req.Fields()...)
	if err != nil {
		return err
	}
	//todo 修改摄像头事件处理
	// event()

	go func() {
		_err := s.subLib.Push(WithTopicToSubscribers(s.subRepo.Subscriber(context.Background()), subscribe.TopicCameraInfo), &subscribe.CameraInfoPayload{m})
		if _err != nil {
			s.log.Error("推送摄像头详情消息失败", zap.Error(_err))
		}
	}()
	return nil
}

func (s *cameraService) Delete(ctx context.Context, ids []int) error {
	res, err := s.cameraRepo.GetByIDs(ctx, ids)
	if err != nil {
		return err
	}
	l := len(res)
	if l == 0 {
		return nil
	}
	ids = make([]int, l)
	idStrs := make([]string, l)
	for i := range res {
		ids[i] = res[i].ID
		idStrs[i] = res[i].DeviceId
	}

	err = s.cameraRepo.DeleteByIDs(ctx, ids)
	if err != nil {
		return err
	}
	//todo 删除摄像头事件处理
	// event()
	go func() {
		_err := s.subLib.Push(WithTopicToSubscribers(s.subRepo.Subscriber(context.Background()), subscribe.TopicCameraInfo), &subscribe.CameraRemovePayload{idStrs})
		if _err != nil {
			s.log.Error("推送删除摄像头消息失败", zap.Error(_err))
		}
	}()
	return nil
}

// SyncDatetime 给摄像头同步系统时间
func (s *cameraService) SyncDatetime(ctx context.Context, req *camera.IdOrDeviceIdReq) (err error) {
	var camera *model.Camera
	if req.Id > 0 {
		camera, err = s.cameraRepo.GetByID(ctx, req.Id)
	} else if req.DeviceId != "" {
		camera, err = s.cameraRepo.GetByDeviceId(ctx, req.DeviceId)
	}
	if err != nil {
		return
	}
	//没有开onvif
	if camera.Addx == "" {
		return nil
	}
	return s.onvif.SetSystemDateAndTime(ctx, &onvif.CameraConnection{
		Addx:     camera.Addx,
		Username: camera.Username,
		Password: camera.Password,
	})
	//cameras, err := s.cameraRepo.GetByIDs(ctx, ids)
	//if err != nil {
	//	return
	//}
	//errChan := make(chan error, len(cameras))
	//for i := range cameras {
	//	go func(camera *model.Camera) {
	//		errChan <- s.onvif.SetSystemDateAndTime(ctx, &onvif.CameraConnection{
	//			Addx:     camera.Addx,
	//			Username: camera.Username,
	//			Password: camera.Password,
	//		})
	//	}(cameras[i])
	//}
	//ers := errs.Group{}
	//for i := 0; i < len(cameras); i++ {
	//	ers.Add(<-errChan)
	//}
	//return ers.Err()
}

func (s *cameraService) GetDatetime(ctx context.Context, req *camera.IdOrDeviceIdReq) (dt time.Time, err error) {
	var camera *model.Camera
	if req.Id > 0 {
		camera, err = s.cameraRepo.GetByID(ctx, req.Id)
	} else if req.DeviceId != "" {
		camera, err = s.cameraRepo.GetByDeviceId(ctx, req.DeviceId)
	}
	if err != nil || camera == nil || camera.ID == 0 {
		return time.Time{}, errors.New("未找到相关摄像头")
	}
	return s.onvif.GetSystemDateAndTime(ctx, &onvif.CameraConnection{
		Addx:     camera.Addx,
		Username: camera.Username,
		Password: camera.Password,
	})
}

func (s *cameraService) Discovery(ctx context.Context) (res []*camera.DiscoveryCameraListRes, err error) {
	res = make([]*camera.DiscoveryCameraListRes, 0)
	ifcs, err := net.Interfaces()
	if err != nil {
		return nil, err
	}
	if len(ifcs) == 0 {
		return
	}
	//选择网络发送onvif广播获取摄像头
	ifcName := s.settingLib.GetWithDefault(setting.NetworkDefault, "").(string)
	exists := false
	for i := range ifcs {
		if ifcs[i].Name == ifcName {
			exists = true
			break
		}
	}
	if !exists {
		if ifcName != "" {
			return
		}
		ifcName = ifcs[0].Name
	}
	cameras, err := s.onvif.DiscoverySummary(ifcName)
	if err != nil {
		return res, err
	}
	if len(cameras) == 0 {
		return
	}
	//查看是否已经添加
	existsCameras, err := s.cameraRepo.All(ctx)
	if err != nil {
		return
	}
	addxs := make(map[string]*model.Camera)
	for i := range existsCameras {
		if existsCameras[i].Addx == "" {
			continue
		}
		addxs[existsCameras[i].Addx] = existsCameras[i]
	}
	for i := range cameras {
		m := camera.DiscoveryCameraListRes{
			Name:   cameras[i].Name,
			Addx:   cameras[i].Addx,
			XAddrs: cameras[i].XAddrs,
			Uuid:   cameras[i].Uuid,
		}
		if v, ok := addxs[m.Addx]; ok {
			m.Exists = true
			m.Id = v.ID
		}
		res = append(res, &m)
	}
	return
}

func (s *cameraService) CheckConnection(ctx context.Context, req *onvif.CameraConnection) (res *onvif.CameraInfo, err error) {
	return s.onvif.CheckConnection(ctx, req)
}

func (s *cameraService) GetM3u8(ctx context.Context, req *camera.GetM3u8Req, dayOrHour string) (res []string, err error) {
	dt := time.Unix(req.Time, 0)
	y, m, d := dt.Date()
	if dayOrHour == "day" {
		res, err = s.m3u8.DayM3u8SplitByHours(req.DeviceId, y, int(m), d)
	} else {
		var f string
		f, err = s.m3u8.GenM3u8byHour(req.DeviceId, y, int(m), d, dt.Hour())
		if err != nil {
			return
		}
		res = []string{f}
	}
	return
}

// CheckCameraOnline 通过获取时间校验摄像头是否在线
func (s *cameraService) CheckCameraOnline(ctx context.Context) {
	list, err := s.cameraRepo.All(ctx)
	if err != nil {
		s.log.Error("CheckCameraOnline error", zap.Error(err))
	}
	s.log.Debug("CheckCameraOnline start", zap.Int("count", len(list)))
	subRes := make(subscribe.CameraOnlinePayload)
	for _, item := range list {
		if item.Addx == "" {
			continue
		}
		_, err = s.onvif.GetSystemDateAndTime(ctx, &onvif.CameraConnection{
			Addx:     item.Addx,
			Username: item.Username,
			Password: item.Password,
		})
		online := true
		if err != nil {
			online = false
		}
		s.log.Debug("CheckCameraOnline", zap.Int("id", item.ID), zap.Bool("old_online", item.Online), zap.Bool("new_online", online))
		if item.Online != online {
			item.Online = online
			if err = s.cameraRepo.Update(ctx, item, "online"); err != nil {
				s.log.Error("update camera online error", zap.Int("id", item.ID), zap.Bool("online", online), zap.Error(err))
			} else {
				subRes[item.DeviceId] = online
			}
		}
	}
	if len(subRes) == 0 {
		return
	}
	go func() {
		_err := s.subLib.Push(WithTopicToSubscribers(s.subRepo.Subscriber(context.Background()), subscribe.TopicCameraOnline), &subRes)
		if _err != nil {
			s.log.Error("推送摄像头在线消息失败", zap.Error(_err))
		}
	}()
}

// UpdateCameraCoverImg 通过获取时间校验摄像头是否在线
func (s *cameraService) UpdateCameraCoverImg(ctx context.Context) {
	list, err := s.cameraRepo.All(ctx)
	if err != nil {
		s.log.Error("UpdateCameraCoverImg error", zap.Error(err))
	}
	s.log.Debug("UpdateCameraCoverImg start", zap.Int("count", len(list)))
	for _, item := range list {
		if item.Addx == "" {
			continue
		}
		select {
		case <-ctx.Done():
			s.log.Info("ctx cancel", zap.Error(ctx.Err()))
			return
		default:
		}
		if err = s.m3u8.CreateCoverImg(item.DeviceId); err != nil {
			s.log.Error("m3u8.CreateCoverImg error", zap.String("deviceId", item.DeviceId), zap.Error(err))
		}
	}
}

// GetMp4ByImg 通过名称获取该时间段的视频
// 输入:https://img.nextstorage.cn/boxalarm/2023/4/13/3CE36B7AE4A6_2023_04_13_16_03_04.jpg
// 返回 /video/3CE36B7AE4A6/tmp/20230413160304.mp4
func (s *cameraService) GetMp4ByImg(ctx context.Context, req *camera.GetMp4ByImgReq) (res string, err error) {
	arr := strings.Split(req.Img, "/")
	img := arr[len(arr)-1]
	img = strings.TrimSuffix(img, ".jpg")
	img = strings.TrimSuffix(img, ".jpeg")
	img = strings.TrimSuffix(img, ".png")
	d := strings.SplitN(img, "_", 2) // # deviceid_YY_mm_dd_HH_MM_SS.jpg
	if len(d) != 2 {
		s.log.Error("WARNING: invalid file name need deviceid_YY_mm_dd_HH_MM_SS.jpg")
		return "", fmt.Errorf("wrong fileKey %s", img)
	}
	deviceID := d[0]
	tsFile, err := time.ParseInLocation(utils.FormatDatetime3, d[1], s.Service.loc)
	if err != nil {
		s.log.Error("WARNING: invalid file name need deviceid_YY_mm_dd_HH_MM_SS.jpg, parse time error")
		return "", fmt.Errorf("wrong fileKey %s", img)
	}
	mp4, _, _, err := s.m3u8.ParseM3u8toMp4(deviceID, tsFile)
	return mp4, err
}

func (s *cameraService) GetHistory(ctx context.Context, req *camera.GetHistoryReq) (res *camera.GetHistoryRes, err error) {
	filelistold, filelist, fileListNew, err := s.m3u8.History(req.DeviceId)
	if err != nil {
		return nil, err
	}

	return &camera.GetHistoryRes{
		Data:        filelistold,
		FileList:    filelist,
		FileListNew: fileListNew,
	}, nil
}

func (s *cameraService) SearchVideo(ctx context.Context, req *camera.VideoSearchReq) (res []m3u8.HistoryFile, err error) {
	timeRange, err := utils.NewTimeRangeFromString(req.StartTime, req.EndTime, utils.FormatDatetime, s.loc)
	if err != nil {
		s.log.Error("utils.NewTimeRangeFromString", zap.String("startTime", req.StartTime), zap.String("endTime", req.EndTime), zap.Error(err))
		return nil, errors.New("时间格式[YYYY-MM-DD HH:mm:ss]错误，解析失败")
	}
	return s.m3u8.SearchVideo(req.DeviceId, timeRange, 30)
}

func (s *cameraService) GetSnapUrl(ctx context.Context, req *camera.GetSnapUrlReq) (res string, err error) {
	//优先走onvif
	if req.Addx != "" {
		res, err = s.onvif.GetSnapshotUri(ctx, &onvif.CameraConnection{
			Addx:     req.Addx,
			Username: req.Username,
			Password: req.Password,
		})
		if err == nil {
			return res, nil
		}
		s.log.Error("GetSnapUrl error", zap.Error(err))
	}
	if req.DeviceId != "" {
		res = fmt.Sprintf("http://127.0.0.1:81/index/api/getSnap?url=rtsp://127.0.0.1:554/live/%s&timeout_sec=%d&expire_sec=%d",
			req.DeviceId, 3, 1)
		return
	}
	return "", errors.New("GetSnapUrl error")
}

func (s *cameraService) AutoHandleMedia(ctx context.Context) {
	list, err := s.cameraRepo.All(ctx)
	if err != nil {
		s.log.Error("AutoHandleMedia error", zap.Error(err))
	}
	s.log.Debug("AutoHandleMedia start", zap.Int("count", len(list)))

	res, err := s.zlMedia.GetMediaList(&zlmedia_sdk.GetMediaListReq{})
	if err != nil {
		s.log.Error("zlMedia.GetMediaList error", zap.Object("res", res), zap.Error(err))
		return
	}
	mapDevice := make(map[string]string, 0)
	for _, v := range res.Data {
		if v.Schema == "rtsp" {
			if !strings.HasSuffix(v.Stream, "_sub") {
				mapDevice[v.Stream] = v.OriginUrl
			}
		}
	}

	s.closeNotinDBStream(ctx, list, mapDevice)

	// 添加流到ZL
	for _, v := range list {
		_, ok := mapDevice[v.DeviceId]
		if !ok {
			select {
			case <-ctx.Done():
				s.log.Info("ctx cancel", zap.Error(ctx.Err()))
				return
			default:
			}
			rtspUrl, err := getStreamUrl(v.RtspUri, v.Username, v.Password)
			if err == nil {
				s.addMedia(v.DeviceId, rtspUrl, MAIN_STREAM, v.Record)
				time.Sleep(time.Second * 2) //给ZL服务一些处理时间
				s.log.Info(fmt.Sprintf("add media main stream rtspuri: %s", rtspUrl))
			}
			rtspUrl, err = getStreamUrl(v.RtspUri1, v.Username, v.Password)
			if err == nil {
				s.addMedia(v.DeviceId, rtspUrl, SUB_STREAM, false)
				time.Sleep(time.Second * 2) //给ZL服务一些处理时间
				s.log.Info(fmt.Sprintf("add media sub stream rtspuri: %s", rtspUrl))
			}
		}
	}

}

func (s *cameraService) closeNotinDBStream(ctx context.Context, dbList []*model.Camera, zlList map[string]string) {
	delMedia := func(deviceId string) {
		res, err := s.zlMedia.DelStreamProxy(&zlmedia_sdk.DelStreamProxyReq{Key: fmt.Sprintf("__defaultVhost__/live/%s", deviceId)})
		if err != nil {
			s.log.Warn(fmt.Sprintf("del to zlmedia server failed camid:%s  %v %s", deviceId, err, res.Msg))
			return
		}
		s.log.Info(fmt.Sprintf("del to zlmedia server ok,  camid: %s", deviceId))
	}
	for deviceId, OriginUrl := range zlList {
		select {
		case <-ctx.Done():
			s.log.Info("ctx cancel", zap.Error(ctx.Err()))
			return
		default:
		}
		bNotinDB := true
		for _, v := range dbList {
			if strings.EqualFold(v.DeviceId, deviceId) {
				uri := strings.Split(v.RtspUri, "rtsp://")
				if len(uri) != 2 {
					s.log.Warn("DEL WRONG RTSPURI", zap.String("RTSPURI", v.RtspUri))
					continue
				}
				// TODO 如果密码中带有‘@’需要做转义
				// 这里只判断主码流地址是否匹配, 如果不匹配则主/辅码流都进行删除
				rtspurl := fmt.Sprintf("rtsp://%s:%s@%s", v.Username, v.Password, uri[1])

				if strings.EqualFold(rtspurl, OriginUrl) {
					bNotinDB = false
					break
				} else {
					s.log.Warn(fmt.Sprintf("diff rtsp url rtspurl:%s OriginUrl=%s", rtspurl, OriginUrl))
				}
			}
		}
		if bNotinDB {
			delMedia(deviceId)
			delMedia(deviceId + "_sub")
			time.Sleep(time.Second * 5) //给ZL服务一些处理时间
		}
	}
}

// addMedia 添加流
// @camid 摄像头唯一ID
// @rtstpuri http://admin:xxxx@192.168.10.1/xxxx/xxx
// @streamtype 流类型 主码流 辅码流
// @save 是否保存视频文件
func (s *cameraService) addMedia(deviceId string, rtspUri string, streamType StreamType, save bool) {
	var stream string
	var iSaveFile int
	if streamType == MAIN_STREAM {
		stream = deviceId
	} else {
		stream = deviceId + "_sub" // 辅码流地址为 camid_sub
	}
	if save {
		iSaveFile = 1
	} else {
		iSaveFile = 0
	}
	res, err := s.zlMedia.AddStreamProxy(&zlmedia_sdk.AddStreamProxyReq{
		Url:       rtspUri,
		Stream:    stream,
		App:       "live",
		Vhost:     "__defaultVhost__",
		EnableHls: iSaveFile,
		//EnableMp4: iSaveFile, // 分布式测试用的是mp4文件
	})
	if err != nil {
		s.log.Error("add to zlmedia server failed", zap.Object("res", res), zap.Error(err))
		return
	}
	s.log.Info(fmt.Sprintf("ZQF INFO: add to zlmedia server ok,  camid: %s uri: %s\n", deviceId, rtspUri))
}

func getStreamUrl(RtspUri string, username string, password string) (string, error) {
	uri := strings.Split(RtspUri, "rtsp://")
	if len(uri) != 2 {
		return "", errors.New("wrong rtsp url")
	}
	rtspUrl := fmt.Sprintf("rtsp://%s:%s@%s", username, password, uri[1])
	return rtspUrl, nil
}
